// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/strategy/order_executor/mm_executor.h"

#include <algorithm>
#include <cmath>
#include <regex>

#include <fmt/core.h>
#include <fmt/format.h>

#include "coin/proto/coin_order_enums.pb.h"
#include "coin2/strategy/order_executor/util.h"

#define EPSILON 1e-6

namespace coin2::strategy::order_executor {

using OrderSide = coin::proto::OrderSide;

bool InBp(double x, double y, double bp) {
  CHECK_GT(x, 0.) << x;
  CHECK_GT(y, 0.) << y;
  CHECK_GT(bp, 0.) << bp;
  return (std::abs(x - y) / (x + y) * 2.) <= (bp * 1e-4);
}

bool InPrice(double x, double y, double delta) {
  CHECK_GE(delta, 0.) << delta;
  return std::abs(x - y) <= delta;
}

bool AcceptByTagPolicy(
    const WorkingOrderState::OrderContext* oc,
    WorkingOrderState::AcceptPolicyType,
    const std::string& filter_tag,
    bool accept_foreign_order) {
  const auto& order_tag = oc->tag();
  // TODO: should accept all if filter_tag is empty (set by func SetMmTag) ?
  // if (filter_tag.empty()) {
  //   return true;
  // }
  if (accept_foreign_order && order_tag == "add_from_order_proto") {
    return true;
  }
  if (order_tag == filter_tag) {
    return true;
  }
  std::smatch dummy_match_result;
  if (std::regex_match(
          order_tag,
          dummy_match_result,
          std::regex(filter_tag))) {  // for collecting orders correctly, throw exception if invalid
                                      // regex format given
    return true;
  }

  return false;
}

PassiveOrderExecutor::PassiveOrderExecutor(
    const PassiveExecutorConfig& config,
    IOrderSubsystem* os,
    std::optional<int64_t> timestamp,
    const TopBook* book,
    GetProductHolderFunc get_product_holder)
    : config_(config), os_(os), mea_(MarketExchangeApi::FromString(config.mea())), book_(book) {
  CHECK_THROW(os_ != nullptr);
  oq_ = os_->GenOrderQuerier();
  SetEnableOrderSubmission(true);

  fire_sell_ = -1;
  fire_buy_ = -1;
  best_ask_ = 0.0;
  best_bid_ = 0.0;
  last_buy_ts_ = 0;
  last_sell_ts_ = 0;

  if (!timestamp) {
    timestamp = GetCurrentTimestamp();
  }
  product_ = CreateProductFromNormString(mea_, config_.symbol(), *timestamp);
  CHECK_THROW(product_) << "Symbol not found!\n" << config_.DebugString();
  CHECK(get_product_holder);
  product_holder_.reset(get_product_holder(*product_, *timestamp)->Clone());
  tick_price_ = &product_holder_->product_info().tick_price();
  tick_qty_ = &product_holder_->product_info().tick_qty();

  if (config.filter_tag().empty()) {
    wo_ = std::make_unique<WorkingOrderState>(product_holder_->product().order_native_symbol(), os);
  } else {
    tag_ = config.filter_tag();
    bool accept_foreign_order = config.accept_foreign_order();
    auto policy = [this, accept_foreign_order](
                      const OrderContext* oc,
                      WorkingOrderState::AcceptPolicyType ftype) -> bool {
      return AcceptByTagPolicy(oc, ftype, this->tag_, accept_foreign_order);
    };
    wo_ = std::make_unique<WorkingOrderState>(
        product_holder_->product().order_native_symbol(),
        os,
        policy);
    LOG(INFO) << "this: " << this << ", filter tag=" << tag_;
  }

  bool need_exchange_id_to_cancel = os_->gateway()->need_exchange_id_to_cancel();
  LOG(INFO) << "Need exchange id to cancel " << need_exchange_id_to_cancel;
  cancel_executor_ = std::make_unique<CancelExecutor>(wo_.get(), need_exchange_id_to_cancel);
  if (config.has_fill_cooldown_period() > 0 && config.fill_cooldown_period() > 0) {
    buy_fills_ =
        std::make_unique<coin2::strategy::util::MovingSum<double>>(config.fill_cooldown_period());
    sell_fills_ =
        std::make_unique<coin2::strategy::util::MovingSum<double>>(config.fill_cooldown_period());
    fill_cooldown_qty_ = config.fill_cooldown_lotsize_cnt() * config.lot_size();
    fill_cooldown_cnt_ = 0;
    if (config.fill_cooldown_cnt() > 0) {
      fill_cooldown_cnt_ = static_cast<size_t>(config.fill_cooldown_cnt());
      buy_fill_cnt_ = std::make_unique<coin2::strategy::util::MovingUnique<int64_t>>(
          config.fill_cooldown_period());
      sell_fill_cnt_ = std::make_unique<coin2::strategy::util::MovingUnique<int64_t>>(
          config.fill_cooldown_period());
    }
    LOG(INFO) << "cooldown period: " << config.fill_cooldown_period()
              << " symbol: " << config_.symbol() << " qty: " << fill_cooldown_qty_
              << " cnt: " << fill_cooldown_cnt_;
  }
  use_fill_pos_ = config.use_fill_pos();
  if (config_.order_qty_book_bp() > 1e-5) {
    CHECK_GT(config_.order_qty_book_ratio(), 0);
    CHECK(book_ != nullptr);
  }
  if (config.has_market_share_limit_window_period() &&
      config.market_share_limit_window_period() > 0) {
    CHECK(config.has_market_share_limit() && config.market_share_limit() > 0);
    market_share_limit_ = config.market_share_limit();
    our_buys_ = std::make_unique<coin2::strategy::util::MovingSum<double>>(
        config.market_share_limit_window_period());
    our_sells_ = std::make_unique<coin2::strategy::util::MovingSum<double>>(
        config.market_share_limit_window_period());
    market_buys_ = std::make_unique<coin2::strategy::util::MovingSum<double>>(
        config.market_share_limit_window_period());
    market_sells_ = std::make_unique<coin2::strategy::util::MovingSum<double>>(
        config.market_share_limit_window_period());
  }
}

int PassiveOrderExecutor::CheckInput(double price, double qty) {
  if (price <= 0) {
    return 1;
  }

  if (qty <= 0) {
    return 2;
  }

  if (qty > config_.lot_size() + 1e-8) {
    return 15;
  }

  return 0;
}

void PassiveOrderExecutor::onBookFeed(const FeedUpdate& upd) {
  if (upd.product() == *product_.get() && upd.book().Bid0().has_value() &&
      upd.book().Ask0().has_value()) {
    double bid0 = upd.book().Bid0().value().price;
    double ask0 = upd.book().Ask0().value().price;
    for (auto* oc : wo_->GetBuyWorkingOrders()) {
      auto order = const_cast<OrderContext*>(static_cast<const OrderContext*>(oc));
      if (order->is_accepted() && !order->IsCancelSent() &&
          order->order_spec().duration != OrderDuration::IOC_ORDER &&
          bid0 < order->order_price() - 1e-6 && !order->implied_fill_published()) {
        double implied_fill_qty = order->order_qty() - order->filled_qty();
        order->HandleFill(
            upd.timestamp(),
            order->order_price(),
            implied_fill_qty,
            fmt::format("{}_implied_book_fill", order->proc_order_id()),
            coin::proto::FillType::MAKER_FILL_TYPE,
            coin::proto::FillSource::IMPLIED_FROM_DEAD,
            std::optional<double>{},
            std::optional<std::string>{});
        LOG(INFO) << fmt::format(
            "Implied fill by book {} {}",
            order->proc_order_id(),
            implied_fill_qty);
      }
    }
    for (auto* oc : wo_->GetSellWorkingOrders()) {
      auto order = const_cast<OrderContext*>(static_cast<const OrderContext*>(oc));
      if (order->is_accepted() && !order->IsCancelSent() &&
          order->order_spec().duration != OrderDuration::IOC_ORDER &&
          ask0 > order->order_price() + 1e-6 && !order->implied_fill_published()) {
        double implied_fill_qty = order->order_qty() - order->filled_qty();
        order->HandleFill(
            upd.timestamp(),
            order->order_price(),
            implied_fill_qty,
            fmt::format("{}_implied_book_fill", order->proc_order_id()),
            coin::proto::FillType::MAKER_FILL_TYPE,
            coin::proto::FillSource::IMPLIED_FROM_DEAD,
            std::optional<double>{},
            std::optional<std::string>{});
        LOG(INFO) << fmt::format(
            "Implied fill by book {} {}",
            order->proc_order_id(),
            implied_fill_qty);
      }
    }
  }
}

void PassiveOrderExecutor::onAccountInfo(const OrderUpdate& upd) {
  if (os_->is_ready()) {
    double og_pos = oq_->position(product());
    if (std::abs(og_pos - og_pos_) > 1.e-6) {
      og_pos_ = og_pos;
      og_pos_ts_ = upd.timestamp();
      LOG(INFO)
          << fmt::format("Update {}.{} og_pos {}", mea_.String(), product().symbol(), og_pos_);
    }
    if ((upd.timestamp() - std::max(og_pos_ts_, last_fill_ts_) >
             config_.update_pos_with_og_pos_period() ||
         last_fill_ts_ == 0) &&
        std::abs(og_pos_ - fill_pos_) > 1.e-6) {
      last_fill_ts_ = upd.timestamp();
      fill_pos_ = og_pos_;
      LOG(INFO) << fmt::format(
          "Update {}.{} fill_pos with og_pos {}",
          mea_.String(),
          product().symbol(),
          og_pos_);
    }
    pos_inited_ = true;
  }
}

void PassiveOrderExecutor::onAccountOrder(const OrderUpdate& upd) {
  if (upd.product_order_info().event().type() == coin::proto::OrderEvent::ORDER_FILLED) {
    last_fill_ts_ = upd.timestamp();
    double fill_qty = upd.product_order_info().event().fill_qty();
    int64_t order_id = upd.product_order_info().event().proc_order_id();
    if (upd.product_order_info().event().trade_side() == coin::proto::TRADE_BUY_SIDE) {
      fill_pos_ += fill_qty;
      if (buy_fills_) {
        buy_fills_->Add(last_fill_ts_, fill_qty);
      }
      if (our_buys_) {
        our_buys_->Add(last_fill_ts_, fill_qty);
      }
      if (buy_fill_cnt_) {
        buy_fill_cnt_->Add(last_fill_ts_, order_id);
      }
    } else if (upd.product_order_info().event().trade_side() == coin::proto::TRADE_SELL_SIDE) {
      fill_pos_ -= fill_qty;
      if (sell_fills_) {
        sell_fills_->Add(last_fill_ts_, fill_qty);
      }
      if (our_sells_) {
        our_sells_->Add(last_fill_ts_, fill_qty);
      }
      if (sell_fill_cnt_) {
        sell_fill_cnt_->Add(last_fill_ts_, order_id);
      }
    }
    LOG(INFO) << fmt::format(
        "{}.{} fill_pos {} {}",
        mea_.String(),
        product().symbol(),
        fill_pos_,
        og_pos_);
  }
}

void PassiveOrderExecutor::onTradeFeed(const FeedUpdate& upd) {
  if (market_buys_) {
    auto ts = upd.timestamp();
    auto qty = upd.trade().fill_qty;
    if (upd.trade().side == TradeSide::TRADE_BUY_SIDE) {
      market_buys_->Add(ts, qty);
    } else if (upd.trade().side == TradeSide::TRADE_SELL_SIDE) {
      market_sells_->Add(ts, qty);
    }
    our_buys_->Update(ts);
    our_sells_->Update(ts);
  }
}

double PassiveOrderExecutor::GetPosition() {
  if (last_fill_ts_ > 0 && use_fill_pos_) {
    return fill_pos_;
  } else {
    return oq_->position(product());
  }
}

void PassiveOrderExecutor::UpdateState(int64_t timestamp) {
  wo_->UpdateOrderState();
  if (buy_fills_) {
    buy_fills_->Update(timestamp);
    sell_fills_->Update(timestamp);
  }
  if (buy_fill_cnt_) {
    buy_fill_cnt_->Update(timestamp);
    sell_fill_cnt_->Update(timestamp);
  }
}

void PassiveOrderExecutor::AdjustRequest(
    double* sell_price_in,
    double* buy_price_in,
    double* sell_qty_in,
    double* buy_qty_in) {
  double& sell_price = *sell_price_in;
  double& buy_price = *buy_price_in;
  double& sell_qty = *sell_qty_in;
  double& buy_qty = *buy_qty_in;
  AdjustQty(&sell_qty, &buy_qty);

  // adjust min_lot_size_ratio, max_lot_size_ratio
  if (sell_qty < config_.lot_size() * config_.min_lot_size_ratio()) {
    sell_qty = 0;
  }

  if (sell_qty > config_.lot_size() * config_.max_lot_size_ratio()) {
    sell_qty = config_.lot_size() * config_.max_lot_size_ratio();
  }

  if (buy_qty < config_.lot_size() * config_.min_lot_size_ratio()) {
    buy_qty = 0;
  }

  if (buy_qty > config_.lot_size() * config_.max_lot_size_ratio()) {
    buy_qty = config_.lot_size() * config_.max_lot_size_ratio();
  }

  if (config_.order_qty_book_bp() > 1e-5) {
    buy_qty = std::min(
        buy_qty,
        book_->BidQtyUpToBp(config_.order_qty_book_bp()) * config_.order_qty_book_ratio());
    sell_qty = std::min(
        sell_qty,
        book_->AskQtyUpToBp(config_.order_qty_book_bp()) * config_.order_qty_book_ratio());
  }

  if (config_.reduce_only()) {
    CHECK(config_.has_reserve());
    double reserve = config_.reserve();
    bool china_style = IsChinaFuturesPositionStyle(mea_);

    if (china_style) {
      CHECK(reserve == 0 && config_.close_only()) << fmt::format(
          "reduce only applied to ChinaStyle mea must have 0 reserve and close only applied {}, {}",
          reserve,
          config_.close_only());
      if (GetTotalLongPosition() > 0 && GetPosition() >= 0) {
        sell_qty = std::min(sell_qty, GetTotalLongPosition());
      } else {
        sell_price = 0;
      }
      if (GetTotalShortPosition() < 0 && GetPosition() <= 0) {
        buy_qty = std::min(buy_qty, -GetTotalShortPosition());
      } else {
        buy_price = 0;
      }
    } else {
      // normal style
      if (GetPosition() > reserve) {
        buy_price = 0;
        sell_qty = std::min(sell_qty, GetPosition() - reserve);
      } else if (GetPosition() < reserve) {
        sell_price = 0;
        buy_qty = std::min(buy_qty, reserve - GetPosition());
      } else {
        buy_price = 0;
        sell_price = 0;
      }
    }
  }

  // adjust product
  if (sell_price > 0) {
    sell_price = tick_price_->RoundWithPolicy(
        sell_price,
        OrderDirection::DIRECTION_SELL,
        config_.rounding_policy());
    if (sell_qty > 0) {
      sell_qty = tick_qty_->Floor(sell_qty);
    }
  }

  if (buy_price > 0) {
    buy_price = tick_price_->RoundWithPolicy(
        buy_price,
        OrderDirection::DIRECTION_BUY,
        config_.rounding_policy());
    if (buy_qty > 0) {
      buy_qty = tick_qty_->Floor(buy_qty);
    }
  }
}

void PassiveOrderExecutor::AdjustQty(double* sell_qty_in, double* buy_qty_in) {
  /* Based on current risk limit, how many *extra* sell/buy can I place */
  double& sell_qty = *sell_qty_in;
  double& buy_qty = *buy_qty_in;

  // adjust default size
  if (sell_qty <= 0) {
    sell_qty = config_.lot_size();
  }

  if (buy_qty <= 0) {
    buy_qty = config_.lot_size();
  }

  // adjust min_pos, max_pos
  if (sell_qty > 0.) {
    sell_qty = std::min(sell_qty, std::max(0.0, (GetPosition() - config_.min_pos())));
  }

  if (buy_qty > 0.) {
    buy_qty = std::min(buy_qty, std::max(0.0, (config_.max_pos() - GetPosition())));
  }
}

void PassiveOrderExecutor::TryFireSellOrder(int64_t timestamp, double sell_price, double sell_qty) {
  if (fire_sell_ != 0) {
    return;
  }
  auto order_side = OrderSide::SELL_ORDER;
  if (config_.allow_amend()) {
    const OrderContext* oc = cancel_executor_->ExtractSell();
    if (oc != nullptr) {
      os_->gateway()->AmendOrder(oc->proc_order_id(), sell_price, NAN);
      last_sell_ts_ = timestamp;
      return;
    }
  }
  if (RiskCheckSell(sell_qty) == 0) {
    if (IsChinaFuturesPositionStyle(mea_)) {
      SubmitChinaOrder(timestamp, order_side, sell_price, sell_qty);
    } else {
      SubmitOrder(timestamp, order_side, sell_price, sell_qty);
    }
  }
}

void PassiveOrderExecutor::TryFireBuyOrder(int64_t timestamp, double buy_price, double buy_qty) {
  if (fire_buy_ != 0) {
    return;
  }
  auto order_side = OrderSide::BUY_ORDER;
  if (config_.allow_amend()) {
    const OrderContext* oc = cancel_executor_->ExtractBuy();
    if (oc != nullptr) {
      os_->gateway()->AmendOrder(oc->proc_order_id(), buy_price, NAN);
      last_buy_ts_ = timestamp;
      return;
    }
  }
  if (RiskCheckBuy(buy_qty) == 0) {
    if (IsChinaFuturesPositionStyle(mea_)) {
      SubmitChinaOrder(timestamp, order_side, buy_price, buy_qty);
    } else {
      SubmitOrder(timestamp, order_side, buy_price, buy_qty);
    }
  }
}

void PassiveOrderExecutor::ForceFireSellOrder(
    int64_t timestamp,
    double sell_price,
    double sell_qty,
    const std::string& tag) {
  bool post_only = false;
  auto order_side = OrderSide::SELL_ORDER;
  const std::string* ptag = &tag;
  if (tag.empty()) {
    ptag = &tag_;
  }
  if (IsChinaFuturesPositionStyle(mea_)) {
    order_side = GetChinaOrderSide(order_side, sell_qty);
  }
  sell_price = tick_price_->RoundAggressively(sell_price, OrderDirection::DIRECTION_SELL);
  CHECK_GT(sell_price, 0.) << fmt::format("sell_price {} sell_qty {}", sell_price, sell_qty);
  double min_qty = tick_qty_->submittable_min_qty(sell_price);
  sell_qty = tick_qty_->Floor(sell_qty);
  if (sell_qty >= min_qty) {
    OrderSpec order_spec(
        product(),
        OrderType::LIMIT_ORDER,
        order_side,
        OrderDuration::IOC_ORDER,
        sell_price,
        sell_qty,
        timestamp,
        post_only,
        config_.leverage(),
        *ptag);
    if (enable_order_submission_) {
      os_->gateway()->SubmitOrder(order_spec);
    }
  }
}

void PassiveOrderExecutor::ForceFireBuyOrder(
    int64_t timestamp,
    double buy_price,
    double buy_qty,
    const std::string& tag) {
  bool post_only = false;
  auto order_side = OrderSide::BUY_ORDER;
  const std::string* ptag = &tag;
  if (tag.empty()) {
    ptag = &tag_;
  }
  if (IsChinaFuturesPositionStyle(mea_)) {
    order_side = GetChinaOrderSide(order_side, buy_qty);
  }
  buy_price = tick_price_->RoundAggressively(buy_price, OrderDirection::DIRECTION_BUY);
  CHECK_GT(buy_price, 0.) << fmt::format("buy_price {} buy_qty {}", buy_price, buy_qty);
  double min_qty = tick_qty_->submittable_min_qty(buy_price);
  buy_qty = tick_qty_->Floor(buy_qty);

  if (buy_qty >= min_qty) {
    OrderSpec order_spec(
        product(),
        OrderType::LIMIT_ORDER,
        order_side,
        OrderDuration::IOC_ORDER,
        buy_price,
        buy_qty,
        timestamp,
        post_only,
        config_.leverage(),
        *ptag);
    if (enable_order_submission_) {
      os_->gateway()->SubmitOrder(order_spec);
    }
  }
}

void PassiveOrderExecutor::CalculateBuyPricePushPull(
    double buy_price,
    double* buy_price_push,
    double* buy_price_pull) {
  *buy_price_push = buy_price * (1 - config_.price_push_bp() * 1.e-4);
  *buy_price_pull = buy_price * (1 + config_.price_pull_bp() * 1.e-4);
}
void PassiveOrderExecutor::CalculateSellPricePushPull(
    double sell_price,
    double* sell_price_push,
    double* sell_price_pull) {
  *sell_price_push = sell_price * (1 + config_.price_push_bp() * 1.e-4);
  *sell_price_pull = sell_price * (1 - config_.price_pull_bp() * 1.e-4);
}

void PassiveOrderExecutor::ThriftPrices(double* buy_price, double* sell_price) {
  if (config_.thrift_ignore_bp() > 1.e-5) {
    double size = config_.thrift_ignore_bp() * 1e-4;
    if (book_->GetBid(0).price * (1 - size) > *buy_price) {
      *buy_price = 0;
    }
    if (book_->GetAsk(0).price * (1 + size) < *sell_price) {
      *sell_price = 0;
    }
  } else if (config_.thrift_ignore_levels() > 0) {
    int level = std::min(book_->BidLevels(), config_.thrift_ignore_levels());
    if (level > 0 && book_->GetBid(level - 1).price > *buy_price + 1e-6) {
      *buy_price = 0;
    }
    level = std::min(book_->AskLevels(), config_.thrift_ignore_levels());
    if (level > 0 && book_->GetAsk(level - 1).price < *sell_price - 1e-6) {
      *sell_price = 0;
    }
  }
}

bool PassiveOrderExecutor::SubmitPassiveOrders(
    int64_t timestamp,
    double sell_price,
    double buy_price,
    double sell_qty,
    double buy_qty,
    bool cancel_all_existing_sell,
    bool cancel_all_existing_buy) {
  UpdateState(timestamp);
  ResetAction();

  double sell_price_pull;
  double sell_price_push;
  double buy_price_push;
  double buy_price_pull;
  CalculateBuyPricePushPull(buy_price, &buy_price_push, &buy_price_pull);
  CalculateSellPricePushPull(sell_price, &sell_price_push, &sell_price_pull);

  ThriftPrices(&buy_price, &sell_price);
  AdjustRequest(&sell_price, &buy_price, &sell_qty, &buy_qty);

  fire_sell_ = SubmitSellOrder(timestamp, sell_price, sell_qty);
  fire_buy_ = SubmitBuyOrder(timestamp, buy_price, buy_qty);

  if (sell_price_pull > 0 || sell_price_push > 0) {
    cancel_executor_->CancelSellByPrice(timestamp, sell_price_pull, sell_price_push);
  }

  if (buy_price_pull > 0 || buy_price_push > 0) {
    cancel_executor_->CancelBuyByPrice(timestamp, buy_price_pull, buy_price_push);
  }

  if (config_.cancel_qty_threshold() > 0) {
    cancel_executor_->CancelSellWithRisk(
        timestamp,
        GetPosition(),
        config_.min_pos(),
        sell_qty * config_.cancel_qty_threshold());
    cancel_executor_->CancelBuyWithRisk(
        timestamp,
        GetPosition(),
        config_.max_pos(),
        buy_qty * config_.cancel_qty_threshold());
  }

  if (cancel_all_existing_sell) {
    cancel_executor_->CancelSellWorkingOrders(timestamp);
  }

  if (cancel_all_existing_buy) {
    cancel_executor_->CancelBuyWorkingOrders(timestamp);
  }

  if (config_.allow_amend()) {
    cancel_executor_->CancelSmallSellOrders(timestamp, sell_qty * config_.qty_refresh_ratio());
    cancel_executor_->CancelSmallBuyOrders(timestamp, buy_qty * config_.qty_refresh_ratio());
  }

  TryFireSellOrder(timestamp, sell_price, sell_qty);
  TryFireBuyOrder(timestamp, buy_price, buy_qty);

  ManageRisk(timestamp);
  CancelOrders();
  return fire_sell_ || fire_buy_;
}

void PassiveOrderExecutor::CancelOrders() {
  cancel_executor_->RemoveDuplicates();
  auto bid_order_list_copy = cancel_executor_->GetCancelBuyList();
  for (const auto& order_id : bid_order_list_copy) {
    os_->gateway()->CancelOrder(order_id);
  }
  auto ask_order_list_copy = cancel_executor_->GetCancelSellList();
  for (const auto& order_id : ask_order_list_copy) {
    os_->gateway()->CancelOrder(order_id);
  }
}

int PassiveOrderExecutor::ForceCancelAllWorkingOrders() {
  int num_orders = 0;
  for (const auto& order : wo_->GetBuyWorkingOrders()) {
    num_orders++;
    os_->gateway()->CancelOrder(order->proc_order_id());
  }
  for (const auto& order : wo_->GetSellWorkingOrders()) {
    num_orders++;
    os_->gateway()->CancelOrder(order->proc_order_id());
  }
  return num_orders;
}

int PassiveOrderExecutor::TryCancelAllWorkingOrders(int64_t timestamp) {
  UpdateState(timestamp);
  int num_orders = wo_->GetBuyWorkingOrders().size() + wo_->GetSellWorkingOrders().size();
  cancel_executor_->CancelOldOrders(timestamp, timestamp);
  CancelOrders();
  return num_orders;
}

int PassiveOrderExecutor::TryCancelAllWorkingOrdersExceptTag(int64_t timestamp, std::string tag) {
  UpdateState(timestamp);
  int num_orders = wo_->GetBuyWorkingOrders().size() + wo_->GetSellWorkingOrders().size();
  cancel_executor_->CancelOldOrdersExceptTag(timestamp, timestamp, tag);
  CancelOrders();
  return num_orders;
}

void PassiveOrderExecutor::ManageRisk(int64_t timestamp) {
  double max_sell_qty = config_.lot_size() * config_.max_working_cnt();
  double max_buy_qty = config_.lot_size() * config_.max_working_cnt();
  AdjustQty(&max_sell_qty, &max_buy_qty);
  cancel_executor_->CancelSellWithRisk(timestamp, GetPosition(), config_.min_pos(), max_sell_qty);
  cancel_executor_->CancelBuyWithRisk(timestamp, GetPosition(), config_.max_pos(), max_buy_qty);
  cancel_executor_->CancelOldOrders(timestamp, timestamp - config_.max_posting_period());
}

void PassiveOrderExecutor::UpdateReduceOnly(double reduce_only) {
  config_.set_reduce_only(reduce_only);
}

void PassiveOrderExecutor::UpdateLotSize(double lot_size) {
  config_.set_lot_size(lot_size);
  fill_cooldown_qty_ = config_.fill_cooldown_lotsize_cnt() * config_.lot_size();
}

void PassiveOrderExecutor::UpdateMinMaxPosition(
    std::optional<double> min_position,
    std::optional<double> max_position,
    int64_t timestamp) {
  bool changed = false;
  if (min_position.has_value() && min_position.value() != config_.min_pos()) {
    LOG(INFO) << fmt::format(
        "{} min Position {}->{}",
        config_.symbol(),
        config_.min_pos(),
        min_position.value());
    config_.set_min_pos(min_position.value());
    changed = true;
  }
  if (max_position.has_value() && max_position.value() != config_.max_pos()) {
    LOG(INFO) << fmt::format(
        "{} max Position {}->{}",
        config_.symbol(),
        config_.max_pos(),
        max_position.value());
    config_.set_max_pos(max_position.value());
    changed = true;
  }
  if (changed && timestamp > 0 && pos_inited_) {
    ManageOrders(timestamp);
  }
}

int PassiveOrderExecutor::RiskCheckSell(double sell_qty) {
  // If current position is too big, do not fire.
  if (GetPosition() - sell_qty < config_.min_pos() - EPSILON) {
    return 4;
  }
  // If potential position is too big, do not fire.
  if (wo_->GetSellPotential() + GetPosition() - sell_qty < config_.min_pos() - EPSILON) {
    return 5;
  }
  return 0;
}

int PassiveOrderExecutor::SubmitSellOrder(int64_t timestamp, double sell_price, double sell_qty) {
  int err_code = CheckInput(sell_price, sell_qty);
  if (err_code != 0) {
    return err_code;
  }

  err_code = RiskCheckSell(0);
  if (err_code != 0) {
    return err_code;
  }

  // If sell potential is too big, do not fire.
  if (-1 * wo_->GetSellPotential() > config_.lot_size() * config_.max_working_cnt() + EPSILON) {
    return 6;
  }

  /* -------------- inflight order check ---------------- */
  // If number of inflight orders are too much, do not fire.
  if (wo_->GetSellUnackCount() > config_.max_inflight_cnt()) {
    return 7;
  }

  // If number of inflight orders are too much, do not fire.
  if (wo_->GetSellCancelUnackCount() > config_.max_inflight_cnt()) {
    return 8;
  }

  // If within order_update_period, do not fire.
  if (timestamp - LastSellTimestamp() < config_.order_update_period()) {
    return 9;
  }

  /* similarity check */
  // If there is an working order that its price is similar to
  // sell_price, keep it and do not fire.
  if (config_.has_sticky_price()) {
    for (auto* order : wo_->GetSellOrders()) {
      const double price = order->order_price();
      if (InPrice(price, sell_price, config_.sticky_price())) {
        return 11;
      }
    }
  } else if (config_.has_sticky_bp()) {
    for (auto* order : wo_->GetSellOrders()) {
      const double price = order->order_price();
      if (InBp(price, sell_price, config_.sticky_bp())) {
        return 12;
      }
    }
  } else {  // exact price match
    for (auto* order : wo_->GetSellOrders()) {
      const double price = order->order_price();
      if (std::abs(price - sell_price) < 1e-8) {
        return 13;
      }
    }
  }

  // If moving fills + potential fills exceed cooldown qty, do not fire.
  if (sell_fills_) {
    if (sell_fills_->GetSum() >= fill_cooldown_qty_) {
      return 14;
    }
  }

  // If exceed market share limit, do not fire.
  if (our_sells_) {
    if (our_sells_->GetSum() >
        (market_sells_->GetSum() + market_buys_->GetSum()) * market_share_limit_) {
      return 15;
    }
  }

  // If exceed number of fills threshold, do not fire
  if (sell_fill_cnt_) {
    if (sell_fill_cnt_->GetUnique() >= fill_cooldown_cnt_) {
      return 16;
    }
  }

  // If qty is too small, do not fire
  // check apply to spot market or non reduce symbol
  if (sell_qty < tick_qty_->submittable_min_qty(sell_price) &&
      (!config_.reduce_only() || mea_.market == MarketType::Spot)) {
    return 17;
  }

  return 0;
}

int PassiveOrderExecutor::RiskCheckBuy(double buy_qty) {
  // If current position is too big, do not fire.
  if (GetPosition() + buy_qty > config_.max_pos() + EPSILON) {
    return 4;
  }

  // If potential position is too big, do not fire.
  if (wo_->GetBuyPotential() + GetPosition() + buy_qty > config_.max_pos() + EPSILON) {
    return 5;
  }
  return 0;
}

int PassiveOrderExecutor::SubmitBuyOrder(int64_t timestamp, double buy_price, double buy_qty) {
  int err_code = CheckInput(buy_price, buy_qty);
  if (err_code != 0) {
    return err_code;
  }

  err_code = RiskCheckBuy(0);
  if (err_code != 0) {
    return err_code;
  }

  // If buy potential is too big, do not fire.
  if (wo_->GetBuyPotential() > config_.lot_size() * config_.max_working_cnt() + EPSILON) {
    return 6;
  }

  /* --------------- inflight order check -------------------*/
  // If number of inflight orders are too much, do not fire.
  if (wo_->GetBuyUnackCount() > config_.max_inflight_cnt()) {
    return 7;
  }

  // If number of inflight orders are too much, do not fire.
  if (wo_->GetBuyCancelUnackCount() > config_.max_inflight_cnt()) {
    return 8;
  }

  /* ----------- throttle ----------------- */
  // If within order_update_period, do not fire.
  if (timestamp - LastBuyTimestamp() < config_.order_update_period()) {
    return 9;
  }

  /* ---------------- similarity check ---------------- */
  // If there is an working order that its price is similar to
  // buy_price, keep it and do not fire.
  if (config_.has_sticky_price()) {
    for (auto* order : wo_->GetBuyOrders()) {
      const double price = order->order_price();
      if (InPrice(price, buy_price, config_.sticky_price())) {
        return 11;
      }
    }
  } else if (config_.has_sticky_bp()) {
    for (auto* order : wo_->GetBuyOrders()) {
      const double price = order->order_price();
      if (InBp(price, buy_price, config_.sticky_bp())) {
        return 12;
      }
    }

  } else {  // exact price match
    for (auto* order : wo_->GetBuyOrders()) {
      const double price = order->order_price();
      if (std::abs(price - buy_price) < 1e-8) {
        return 13;
      }
    }
  }

  // If moving fills + potential fills exceed cooldown qty, do not fire.
  if (buy_fills_) {
    if (buy_fills_->GetSum() >= fill_cooldown_qty_) {
      return 14;
    }
  }

  // If exceed market share limit, do not fire.
  if (our_buys_) {
    if (our_buys_->GetSum() >
        (market_sells_->GetSum() + market_buys_->GetSum()) * market_share_limit_) {
      return 15;
    }
  }

  // If exceed number of fills threshold, do not fire
  if (buy_fill_cnt_) {
    if (buy_fill_cnt_->GetUnique() >= fill_cooldown_cnt_) {
      return 16;
    }
  }

  // If qty is smaller than tick, do not fire
  // check apply to spot market or non reduce symbol
  if (buy_qty < tick_qty_->submittable_min_qty(buy_price) &&
      (!config_.reduce_only() || mea_.market == MarketType::Spot)) {
    return 17;
  }

  /*----------------- fire -----------------*/
  return 0;
}

void PassiveOrderExecutor::SubmitOrder(
    int64_t timestamp,
    OrderSide order_side,
    double order_price,
    double order_qty) {
  constexpr bool post_only = true;
  std::string side;
  if (order_side == OrderSide::BUY_ORDER) {
    side = "BUY_ORDER";
  } else {
    side = "SELL_ORDER";
  }
  LOG(INFO) << side << ": " << order_price << "/" << order_qty;
  OrderSpec order_spec(
      product(),
      OrderType::LIMIT_ORDER,
      order_side,
      OrderDuration::GTC_ORDER,
      order_price,
      order_qty,
      timestamp,
      post_only,
      config_.leverage(),
      tag_);
  if (config_.reduce_only()) {
    order_spec.SetReduceOnly(true);
  }
  if (enable_order_submission_) {
    os_->gateway()->SubmitOrder(order_spec);
  }

  if (order_side == OrderSide::BUY_ORDER) {
    last_buy_ts_ = timestamp;
  } else if (order_side == OrderSide::SELL_ORDER) {
    last_sell_ts_ = timestamp;
  } else {
    NOTREACHED();
  }
}

OrderSide PassiveOrderExecutor::GetChinaOrderSide(OrderSide order_side, double order_qty) {
  if (order_side == OrderSide::BUY_ORDER) {
    if (config_.close_only() || GetShortPosition() + wo_->GetBuyPotential() + order_qty <= 0.0) {
      order_side = OrderSide::BUY_CLOSE_ORDER;
    } else {
      order_side = OrderSide::BUY_OPEN_ORDER;
    }
  } else if (order_side == OrderSide::SELL_ORDER) {
    if (config_.close_only() || GetLongPosition() + wo_->GetSellPotential() - order_qty >= 0.0) {
      order_side = OrderSide::SELL_CLOSE_ORDER;
    } else {
      order_side = OrderSide::SELL_OPEN_ORDER;
    }
  } else {
    NOTREACHED();
  }
  return order_side;
}

void PassiveOrderExecutor::SubmitChinaOrder(
    int64_t timestamp,
    OrderSide order_side,
    double order_price,
    double order_qty) {
  constexpr bool post_only = true;
  auto china_order_side = GetChinaOrderSide(order_side, order_qty);
  const std::string& side = ::coin::proto::OrderSide_Name(china_order_side);
  LOG(INFO) << side << ": " << order_price << "/" << order_qty;
  OrderSpec order_spec(
      product(),
      OrderType::LIMIT_ORDER,
      china_order_side,
      OrderDuration::GTC_ORDER,
      order_price,
      order_qty,
      timestamp,
      post_only,
      config_.leverage(),
      tag_);
  if (config_.reduce_only()) {
    order_spec.SetReduceOnly(true);
  }
  if (enable_order_submission_) {
    os_->gateway()->SubmitOrder(order_spec);
  }
  if (order_side == OrderSide::BUY_ORDER) {
    last_buy_ts_ = timestamp;
  } else if (order_side == OrderSide::SELL_ORDER) {
    last_sell_ts_ = timestamp;
  } else {
    NOTREACHED();
  }
}

void PassiveOrderExecutor::LogStatus() const {
  LOG(INFO) << fmt::format(
      "{} Buy/SellCount {}/{}, Buy/SellPotential {}/{}, Buy/SellUnackCount {}/{}, "
      "Buy/SellCancelUnackCount "
      "{}/{},Buy/SellUnackQty {}/{}",
      config_.symbol(),
      wo_->GetBuyOrders().size(),
      wo_->GetSellOrders().size(),
      wo_->GetBuyPotential(),
      wo_->GetSellPotential(),
      wo_->GetBuyUnackCount(),
      wo_->GetSellUnackCount(),
      wo_->GetBuyCancelUnackCount(),
      wo_->GetSellCancelUnackCount(),
      wo_->GetBuyUnackQty(),
      wo_->GetSellUnackQty());
  if (market_buys_) {
    double limit_qty = (market_sells_->GetSum() + market_buys_->GetSum()) * market_share_limit_;
    LOG(INFO) << fmt::format(
        "market share buy/sell/limit {} / {} / {}",
        our_buys_->GetSum(),
        our_sells_->GetSum(),
        limit_qty);
  }
}

void PassiveOrderExecutor::ManageOrders(int64_t timestamp) {
  UpdateState(timestamp);
  ResetAction();
  ManageRisk(timestamp);
  CancelOrders();
}

void PassiveOrderExecutor::ManageMmOrders(
    int64_t timestamp,
    double sell_price,
    double buy_price,
    double sell_qty,
    double buy_qty,
    bool cancel_all_existing_sell,
    bool cancel_all_existing_buy) {
  if (pos_inited_) {
    SubmitPassiveOrders(
        timestamp,
        sell_price,
        buy_price,
        sell_qty,
        buy_qty,
        cancel_all_existing_sell,
        cancel_all_existing_buy);
  }
}
}  // namespace coin2::strategy::order_executor
